package net.esj.basic.core.engine.support.message;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.esj.basic.utils.Validators;

public abstract class AbstractMessageProvider implements MessageProvider {
	
	private static final MessageFormat INVALID_MESSAGE_FORMAT = new MessageFormat("");
	
	private MessageProvider parentMessageSource;
	
	private final Map<String, MessageFormat> cachedMessageFormats = new HashMap<String, MessageFormat>(); 
	
	@Override
	public String getMessage(String sourceName,String code, Object[] args, String defaultMessage,
			Locale locale) {
		String msg = getMessageInternal(sourceName,code, args, locale);
		if (msg != null) {
			return msg;
		}
		if (defaultMessage == null) {
			String fallback = defaultMessage;
			if (fallback != null) {
				return fallback;
			}
		}
		return getDefaultMessage(sourceName,defaultMessage, args, locale);
	}

	@Override
	public String getMessage(String sourceName,String code, Object[] args, Locale locale)
			throws NoSuchMessageException {
		return getMessage(sourceName,code, args,null, locale);
	}
	
	@Override
	public String getMessage(String sourceName,MessageResolver resolver, Locale locale)
			throws NoSuchMessageException {
		String[] codes = resolver.getCodes();
		if (codes == null) {
			codes = new String[0];
		}
		for (String code : codes) {
			String msg = getMessageInternal(sourceName,code, resolver.getArguments(), locale);
			if (msg != null) {
				return msg;
			}
		}
		String defaultMessage = resolver.getDefaultMessage();
		if (defaultMessage != null) {
			return getDefaultMessage(sourceName,defaultMessage, resolver.getArguments(), locale);
		}
		if (codes.length > 0) {
			String fallback = codes[0];
			if (fallback != null) {
				return fallback;
			}
		}
		throw new NoSuchMessageException(codes.length > 0 ? codes[codes.length - 1] : null, locale);
	}

	protected String getMessageInternal(String sourceName,String code, Object[] args, Locale locale) {
		if (code == null) {
			return null;
		}
		if (locale == null) {
			locale = Locale.getDefault();
		}
		Object[] argsToUse = args;

		if (Validators.isEmpty(args)) {
			String message = resolveCodeWithoutArguments(sourceName,code, locale);
			if (message != null) {
				return message;
			}
		}
		else {
			argsToUse = resolveArguments(sourceName,args, locale);

			MessageFormat messageFormat = resolveCode(sourceName,code, locale);
			if (messageFormat != null) {
				synchronized (messageFormat) {
					return messageFormat.format(argsToUse);
				}
			}
		}

		return getMessageFromParent(sourceName,code, argsToUse, locale);
	}
	
	
	protected String getMessageFromParent(String sourceName,String code, Object[] args, Locale locale) {
		MessageProvider parent = getParentMessageSource();
		if (parent != null) {
			if (parent instanceof AbstractMessageProvider) {
				return ((AbstractMessageProvider) parent).getMessageInternal(sourceName,code, args, locale);
			}
			else {
				return parent.getMessage(sourceName,code, args, null, locale);
			}
		}
		return null;
	}
	
	protected String resolveCodeWithoutArguments(String sourceName,String code, Locale locale) {
		MessageFormat messageFormat = resolveCode(sourceName,code, locale);
		if (messageFormat != null) {
			synchronized (messageFormat) {
				return messageFormat.format(new Object[0]);
			}
		}
		return null;
	}
	
	protected Object[] resolveArguments(String sourceName,Object[] args, Locale locale) {
		if (args == null) {
			return new Object[0];
		}
		List<Object> resolvedArgs = new ArrayList<Object>(args.length);
		for (Object arg : args) {
			if (arg instanceof MessageResolver) {
				resolvedArgs.add(getMessage(sourceName,(MessageResolver) arg, locale));
			}
			else {
				resolvedArgs.add(arg);
			}
		}
		return resolvedArgs.toArray(new Object[resolvedArgs.size()]);
	}
	
	protected String getDefaultMessage(String sourceName,String msg, Object[] args, Locale locale)  {
		MessageFormat messageFormat;
		synchronized (this.cachedMessageFormats) {
			messageFormat = this.cachedMessageFormats.get(msg);
			if (messageFormat == null) {
				try {
					messageFormat = createMessageFormat(msg, locale);
				}
				catch (IllegalArgumentException ex) {
					messageFormat = INVALID_MESSAGE_FORMAT;
				}
				this.cachedMessageFormats.put(msg, messageFormat);
			}
		}
		if (messageFormat == INVALID_MESSAGE_FORMAT) {
			return msg;
		}
		synchronized (messageFormat) {
			return messageFormat.format(resolveArguments(sourceName,args, locale));
		}
	}
	
	protected MessageFormat createMessageFormat(String msg, Locale locale) {
		return new MessageFormat((msg != null ? msg : ""), locale);
	}
	
	protected abstract MessageFormat resolveCode(String sourceName,String code, Locale locale);
	
	public MessageProvider getParentMessageSource() {
		return parentMessageSource;
	}

	public void setParentMessageSource(MessageProvider parentMessageSource) {
		this.parentMessageSource = parentMessageSource;
	}

}
